import os
import re
import stat
import time
import threading
from datetime import datetime
from typing import List, Dict, Any, Callable, Optional, Tuple

try:
    import magic
except ImportError:
    magic = None


class FileSearchEngine:
    """文件搜索引擎核心类"""
    
    def __init__(self):
        self.search_results = []
        self.is_searching = False
        self.stop_requested = False
        self._search_thread = None
        
    def search(self, 
              root_dir: str, 
              filename_pattern: str = "", 
              content_pattern: str = "",
              min_size: Optional[int] = None, 
              max_size: Optional[int] = None,
              min_date: Optional[datetime] = None, 
              max_date: Optional[datetime] = None,
              file_types: List[str] = None,
              max_results: int = 1000,
              progress_callback: Callable[[int, str], None] = None) -> None:
        """
        开始异步搜索文件
        
        参数:
            root_dir: 搜索的根目录
            filename_pattern: 文件名正则表达式模式
            content_pattern: 文件内容正则表达式模式
            min_size: 最小文件大小(字节)
            max_size: 最大文件大小(字节)
            min_date: 最早修改日期
            max_date: 最晚修改日期
            file_types: 文件类型列表
            max_results: 最大结果数
            progress_callback: 进度回调函数，接收(已处理文件数, 当前处理文件路径)
        """
        if self.is_searching:
            self.stop_search()
            # 等待前一个搜索结束
            if self._search_thread:
                self._search_thread.join()
        
        self.search_results = []
        self.is_searching = True
        self.stop_requested = False
        
        # 启动搜索线程
        self._search_thread = threading.Thread(
            target=self._search_worker,
            args=(root_dir, filename_pattern, content_pattern, min_size, max_size,
                 min_date, max_date, file_types, max_results, progress_callback)
        )
        self._search_thread.daemon = True
        self._search_thread.start()
    
    def _search_worker(self, 
                      root_dir: str, 
                      filename_pattern: str,
                      content_pattern: str,
                      min_size: Optional[int], 
                      max_size: Optional[int],
                      min_date: Optional[datetime], 
                      max_date: Optional[datetime],
                      file_types: List[str],
                      max_results: int,
                      progress_callback: Callable[[int, str], None]) -> None:
        """搜索工作线程实现"""
        try:
            count = 0
            
            # 编译正则表达式以提高性能
            filename_regex = re.compile(filename_pattern, re.IGNORECASE) if filename_pattern else None
            content_regex = re.compile(content_pattern, re.IGNORECASE) if content_pattern else None
            
            for root, dirs, files in os.walk(root_dir):
                if self.stop_requested:
                    break
                    
                # 跳过隐藏文件夹
                dirs[:] = [d for d in dirs if not d.startswith('.')]
                
                for filename in files:
                    if self.stop_requested or len(self.search_results) >= max_results:
                        break
                        
                    full_path = os.path.join(root, filename)
                    
                    # 更新进度
                    count += 1
                    if progress_callback:
                        progress_callback(count, full_path)
                    
                    # 检查文件名匹配
                    if filename_regex and not filename_regex.search(filename):
                        continue
                    
                    try:
                        # 获取文件信息
                        file_stat = os.stat(full_path)
                        file_size = file_stat.st_size
                        mod_time = datetime.fromtimestamp(file_stat.st_mtime)
                        
                        # 检查文件大小
                        if (min_size is not None and file_size < min_size) or \
                           (max_size is not None and file_size > max_size):
                            continue
                            
                        # 检查修改日期
                        if (min_date is not None and mod_time < min_date) or \
                           (max_date is not None and mod_time > max_date):
                            continue
                            
                        # 检查文件类型
                        file_type = self._get_file_type(full_path)
                        if file_types and file_type not in file_types:
                            continue
                            
                        # 检查文件内容
                        if content_regex and not self._check_file_content(full_path, content_regex):
                            continue
                            
                        # 添加到结果
                        result = {
                            'path': full_path,
                            'name': filename,
                            'size': file_size,
                            'modified': mod_time,
                            'type': file_type
                        }
                        self.search_results.append(result)
                        
                    except (PermissionError, FileNotFoundError):
                        # 跳过无法访问的文件
                        continue
                        
            # 搜索结束
            self.is_searching = False
            
        except Exception as e:
            print(f"搜索错误: {str(e)}")
            self.is_searching = False
    
    def stop_search(self) -> None:
        """停止当前搜索"""
        self.stop_requested = True
        
    def get_results(self) -> List[Dict[str, Any]]:
        """获取当前搜索结果"""
        return self.search_results.copy()
    
    @staticmethod
    def _check_file_content(filepath: str, pattern: re.Pattern) -> bool:
        """检查文件内容是否匹配模式"""
        try:
            # 简单判断是否是二进制文件
            is_binary = False
            try:
                with open(filepath, 'r', errors='strict') as f:
                    f.read(1024)
            except UnicodeDecodeError:
                is_binary = True
                
            if is_binary:
                return False
                
            with open(filepath, 'r', errors='ignore') as f:
                # 分块读取大文件
                chunk_size = 8192
                while True:
                    chunk = f.read(chunk_size)
                    if not chunk:
                        break
                    if pattern.search(chunk):
                        return True
                        
            return False
            
        except (PermissionError, FileNotFoundError):
            return False
    
    @staticmethod
    def _get_file_type(filepath: str) -> str:
        """获取文件类型"""
        # 使用文件扩展名作为基本类型识别
        ext = os.path.splitext(filepath)[1].lower()
        
        # 如果有python-magic库，使用更准确的MIME类型检测
        if magic:
            try:
                mime = magic.from_file(filepath, mime=True)
                return mime
            except:
                pass
                
        # 根据扩展名映射常见文件类型
        ext_map = {
            '.txt': 'text/plain',
            '.pdf': 'application/pdf',
            '.doc': 'application/msword',
            '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            '.xls': 'application/vnd.ms-excel',
            '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            '.ppt': 'application/vnd.ms-powerpoint',
            '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            '.jpg': 'image/jpeg',
            '.jpeg': 'image/jpeg',
            '.png': 'image/png',
            '.gif': 'image/gif',
            '.mp3': 'audio/mpeg',
            '.mp4': 'video/mp4',
            '.html': 'text/html',
            '.htm': 'text/html',
            '.py': 'text/x-python',
            '.js': 'application/javascript',
            '.css': 'text/css',
            '.json': 'application/json',
            '.xml': 'application/xml',
            '.zip': 'application/zip',
            '.rar': 'application/x-rar-compressed',
            '.exe': 'application/x-msdownload',
            '.dll': 'application/x-msdownload',
        }
        
        return ext_map.get(ext, f"application/octet-stream") 